package pl.softech.gpw.stat;

/**
 * Average Directional Index (ADX)
 * http://en.wikipedia.org/wiki/Average_Directional_Index
 */
public class Adx {

    private interface Function {
        double[] count(double[] arg);
    }

    private static double upMove(double highLast, double highNow) {

        double tmp = highNow - highLast;

        return tmp < 0 ? 0 : tmp;
    }

    private static double downMove(double lowLast, double lowNow) {

        double tmp = lowLast - lowNow;

        return tmp < 0 ? 0 : tmp;

    }

    private static double tr(double low, double high, double prevClose) {
        return Math.max(high, prevClose) - Math.min(low, prevClose);
    }

    public static double[] count(double[] low, double[] high, double[] close, final double weight, final int n) {
        
        return count(low, high, close, new Function() {
            
            @Override
            public double[] count(double[] arg) {
                return AverageIndexes.ema(arg, weight, n);
            }
        });
    }
    
    public static double[] count(double[] low, double[] high, double[] close,  final int n) {
        
        return count(low, high, close, new Function() {
            
            @Override
            public double[] count(double[] arg) {
                return AverageIndexes.sma(arg, n);
            }
        });
    }
    
    private static double[] count(double[] low, double[] high, double[] close, Function function) {

        double[] ret = new double[close.length];

        double[] plusDI = new double[close.length];
        double[] minusDI = new double[close.length];

        plusDI[0] = 0;
        minusDI[0] = 0;

        for (int i = 1; i < ret.length; i++) {

            double upMove = upMove(high[i - 1], high[i]);
            double downMove = downMove(low[i - 1], low[i]);

            double plusDM = 0;
            double minusDM = 0;

            if (upMove > downMove && upMove > 0) {
                plusDM = upMove;
            }

            if (downMove > upMove && downMove > 0) {
                minusDM = downMove;
            }

            double tr = tr(low[i], high[i], close[i - 1]);

            plusDI[i] = plusDM / tr;
            minusDI[i] = minusDM / tr;

        }

        plusDI = function.count(plusDI);
        minusDI = function.count(minusDI);

        ret[0] = 0;
        for (int i = 1; i < ret.length; i++) {

            double m = plusDI[i] + minusDI[i];

            if (m == 0) {
                ret[i] = 0;
                continue;
            }

            ret[i] = 100 * (plusDI[i] - minusDI[i]) / m;
        }

        return function.count(ret);

    }

}
